﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
using System.Collections;
using System.Diagnostics;
using Cosmos;
using Cosmos.IO;

namespace Cosmos.Windows.Forms.FileList
{
    /// <summary>
    /// Represents a Windows list view control, which display a collection of file names and their thumbnails. by a ListView.
    /// </summary>
    /// <remarks>The thumbnail icons of list items will be drawn in virtual mode.</remarks>
    [DefaultEvent("SelectedChanged")]
    public partial class FileThumbnailVirtualListView : UserControl, IFileList
    {
        #region Fields

        /// <summary>
        /// The items of fileThumbListView are being selected by some method, and the event 'SelectedIndexChanged' should do nothing.
        /// </summary>
        private bool _isSelectingListView;

        /// <summary>
        /// Prepare to rename a file. When the item in listView is renamed, event 'SelectedIndexChanged' should do nothing.
        /// </summary>
        private bool _isRenamingFile;

        /// <summary>
        /// Prepare to refresh a thumb. When the in listView is refreshed, event 'SelectedIndexChanged' should do nothing.
        /// </summary>
        private bool _isRefreshingThumbnail;

        /// <summary>
        /// The files can be retrieved.
        /// </summary>
        private List<string> _pathList;
        /// <summary>
        /// The cache of listView using virtual mode.
        /// </summary>
        private Hashtable _itemCache;
        /// <summary>
        /// The queue containing the thumb files.
        /// </summary>
        private Queue<string> _pathQueue;
        /// <summary>
        /// The stack containing the thumb files.
        /// </summary>
        private Stack<string> _pathStack;
        /// <summary>
        /// The hash table which contains icon thumbnail images which had been drawn.
        /// </summary>
        private Hashtable _iconTable;
        /// <summary>
        /// The hash set which contains added paths.
        /// </summary>
        private HashSet<string> _pathSet;
        /// <summary>
        /// The key of default thumb image of image file. The default image will be used before the correct thumb image being drawn.
        /// </summary>
        private const string _imageFileThumbnailKey = "*Default Image File*";
        /// <summary>
        /// The key of default thumb image of data file. The default image will be used before the correct thumb image being drawn.
        /// </summary>
        private const string _dataFieThumbnailKey = "*Default Data File*";
        /// <summary>
        /// Column name of list view in default status.
        /// </summary>
        private const string _fileNameColumnName = "FileName";

        private FileNameAttribute _fileNameAttribute;

        /// <summary>
        /// whether the last requested will be drawn firstly when there are many requested image.
        /// </summary>
        private bool _virtualModeLastThumbFirstDraw;

        /// <summary>
        /// A value indicating whether the small thumbnail icon is enabled.
        /// </summary>
        private bool _smallThumbnailIcon;
        /// <summary>
        /// A value indicating whether the large thumbnail icon is enabled.
        /// </summary>
        private bool _largeThumbnailIcon;
        /// <summary>
        /// Default thumbnail of image file. 
        /// It will be displayed before the thumbnail has been loaded or when the thumbnail has been disabled.
        /// </summary>
        private Image _imageFileDefaultIcon;

        /// <summary>
        /// Default icon of data file.
        /// It will be displayed before the thumbnail has been loaded or when the thumbnail has been disabled.
        /// </summary>
        private Image _dataFileDefaultIcon;

        #endregion

        /// <summary>
        /// Initializes a new instance of FileThumbnailVirtualListView class.
        /// </summary>
        public FileThumbnailVirtualListView()
        {
            InitializeComponent();

            listView.VirtualMode = true;
            listView.VirtualListSize = 0;
            listView.AllowColumnReorder = true;
            listView.FullRowSelect = true;
            listView.HideSelection = false;
            listView.Columns.Add(_fileNameColumnName, "文件名");
            listView.LargeImageList = largeImageList;
            listView.SmallImageList = smallImageList;

            _virtualModeLastThumbFirstDraw = true;
            _smallThumbnailIcon = true;
            _largeThumbnailIcon = true;

            _iconTable = new Hashtable();
            _pathSet = new HashSet<string>();
            _pathList = new List<string>();
            _itemCache = new Hashtable();
            _pathQueue = new Queue<string>();
            _pathStack = new Stack<string>();
        }

        #region  FileList Methods

        public void SelectPrevious()
        {
            IsSelectingListView = true;
            if (listView.SelectedIndices.Count == 0)
            {
                int listSize = Count;
                if (listSize == 0)
                    return;

                listView.SelectedIndices.Add(listSize - 1);
                listView.EnsureVisible(listSize - 1);
            }
            else
            {
                int fileIndex = listView.SelectedIndices[0];
                listView.SelectedIndices.Clear();
                int listSize = Count;
                fileIndex = (--fileIndex + listSize) % listSize;
                listView.SelectedIndices.Add(fileIndex);
                listView.EnsureVisible(fileIndex);
            }
            IsSelectingListView = false;

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SelectNext()
        {
            IsSelectingListView = true;
            if (listView.SelectedIndices.Count == 0)
            {
                int listSize = Count;
                if (listSize == 0)
                    return;

                listView.SelectedIndices.Add(0);
                listView.EnsureVisible(0);
            }
            else
            {
                int fileIndex = listView.SelectedIndices[0];
                listView.SelectedIndices.Clear();
                int listSize = Count;
                fileIndex = (++fileIndex) % listSize;
                listView.SelectedIndices.Add(fileIndex);
                listView.EnsureVisible(fileIndex);
            }
            IsSelectingListView = false;

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SelectNone()
        {
            IsSelectingListView = true;
            listView.SelectedIndices.Clear();
            IsSelectingListView = false;

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SelectAll()
        {
            IsSelectingListView = true;
            int listSize = Count;
            for (int i = 0; i < listSize; i++)
                listView.SelectedIndices.Add(i);
            IsSelectingListView = false;

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SelectInvert()
        {
            IsSelectingListView = true;
            int listSize = Count;
            for (int i = 0; i < listSize; i++)
            {
                if (listView.SelectedIndices.Contains(i))
                    listView.SelectedIndices.Remove(i);
                else
                    listView.SelectedIndices.Add(i);
            }
            IsSelectingListView = false;

            OnSelectedChanged(EventArgs.Empty);
        }

        public int GetSelectedIndex()
        {
            if (listView.SelectedIndices.Count != 0)
                return listView.SelectedIndices[0];
            else
                return -1;
        }

        public int[] GetSelectedIndices()
        {
            int count = listView.SelectedIndices.Count;
            int[] selectedIndices = new int[count];
            for (int i = 0; i < count; i++)
            {
                selectedIndices[i] = listView.SelectedIndices[i];
            }
            return selectedIndices;
        }

        public void SetSelectedIndex(int index)
        {
            SelectIndex(index, true);
            EnsureVisible(index);

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SetSelectedIndices(int[] indices)
        {
            SelectIndices(indices, true);
            if (indices.Length > 0)
                EnsureVisible(indices[0]);

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SetSelectedPath(string path)
        {
            SelectPath(path, true);
            EnsureVisible(path);

            OnSelectedChanged(EventArgs.Empty);
        }

        public void SetSelectedPaths(string[] paths)
        {
            SelectPaths(paths, true);
            if (paths.Length > 0)
                EnsureVisible(paths[0]);

            OnSelectedChanged(EventArgs.Empty);
        }

        public string GetSelectedPath()
        {
            if (listView.SelectedIndices.Count != 0)
                return _pathList[listView.SelectedIndices[0]];
            else
                return null;
        }

        public string[] GetSelectedPaths()
        {
            int count = listView.SelectedIndices.Count;
            string[] selectedPaths = new string[count];
            for (int i = 0; i < count; i++)
            {
                selectedPaths[i] = _pathList[listView.SelectedIndices[i]];
            }
            return selectedPaths;
        }

        public string GetPathAt(int index)
        {
            if (index >= 0 && index < Count)
                return _pathList[index];
            else
                return null;
        }

        public int IndexOf(string path)
        {
            int index = -1;
            int fileCount = _pathList.Count;
            for (int i = 0; i < fileCount; i++)
            {
                if (_pathList[i].Equals(path))
                {
                    index = i;
                    break;
                }
            }
            return index;
        }

        public bool Contains(string path)
        {
            return _pathSet.Contains(path);
        }

        public void Clear()
        {
            lock (_pathQueue)
            {
                _pathQueue.Clear();
            }

            lock (_pathStack)
            {
                _pathStack.Clear();
            }

            if (Count > 0)
                listView.EnsureVisible(0);
            listView.VirtualListSize = 0;

            _pathSet.Clear();
            _pathList.Clear();
            _itemCache.Clear();

            //Load ImageList.  
            largeImageList.Images.RemoveByKey(_imageFileThumbnailKey);
            smallImageList.Images.RemoveByKey(_imageFileThumbnailKey);
            largeImageList.Images.RemoveByKey(_dataFieThumbnailKey);
            smallImageList.Images.RemoveByKey(_dataFieThumbnailKey);
            largeImageList.Images.Clear();
            smallImageList.Images.Clear();
            if (_imageFileDefaultIcon != null)
            {
                largeImageList.Images.Add(_imageFileThumbnailKey, _imageFileDefaultIcon);
                smallImageList.Images.Add(_imageFileThumbnailKey, _imageFileDefaultIcon);
            }
            if (_dataFileDefaultIcon != null)
            {
                largeImageList.Images.Add(_dataFieThumbnailKey, _dataFileDefaultIcon);
                smallImageList.Images.Add(_dataFieThumbnailKey, _dataFileDefaultIcon);
            }
            OnPathCleared(EventArgs.Empty);
        }

        public int Add(string path)
        {
            int theIndex = -1;
            if (path != null && !Contains(path))
            {
                lock (_pathList)
                {
                    _pathList.Add(path);
                    listView.VirtualListSize++;
                }
                theIndex = listView.VirtualListSize - 1;
                _pathSet.Add(path);
                OnPathAdded(new PathEventArgs(path));
            }
            return theIndex;
        }

        public void AddRange(IEnumerable<string> paths)
        {
            int fileCount = 0;
            List<string> addedPathList = null;
            if (paths is ICollection<string>)
            {
                fileCount = ((ICollection<string>)paths).Count;
                // Ensure the capacity of _pathList is empty for new paths.
                int capacity = _pathList.Capacity + fileCount;
                lock (_pathList)
                {
                    if (_pathList.Capacity < capacity)
                        _pathList.Capacity = capacity;
                }
                addedPathList = new List<string>(fileCount);
            }
            else
            {
                /*foreach (string aPath in paths)
                    fileCount++;*/
                addedPathList = new List<string>();
            }

            lock (_pathList)
            {
                foreach (string aPath in paths)
                {
                    if (aPath != null && !Contains(aPath))
                    {
                        _pathList.Add(aPath);
                        _pathSet.Add(aPath);
                        addedPathList.Add(aPath);
                    }
                }
            }
            listView.VirtualListSize = _pathList.Count;
            OnPathsAdded(new PathsEventArgs(addedPathList.ToArray()));
        }

        public int Insert(int index, string path)
        {
            int theIndex = -1;
            if ((index >= 0 && index < Count) && !Contains(path))
            {
                lock (_pathList)
                {
                    _pathList.Insert(index, path);
                    listView.VirtualListSize++;
                    theIndex = index;
                }
                _pathSet.Add(path);
                OnPathInserted(new PathInsertedEventArgs(index, path));
            }
            return theIndex;
        }

        public int Remove(string path)
        {
            int theIndex = IndexOf(path);
            if (theIndex != -1)
            {
                lock (_pathList)
                {
                    _pathList.RemoveAt(theIndex);
                    listView.VirtualListSize--;
                }
                _pathSet.Remove(path);
                _itemCache.Remove(path);
                // Do not remove the thumbnail, or the index in ImageList will be upset.

                OnPathRemoved(new PathEventArgs(path));
            }
            return theIndex;
        }

        public int RemoveAt(int index)
        {
            int theIndex = -1;
            string path = null;
            if (index >= 0 && index < Count)
            {
                path = _pathList[index];
                lock (_pathList)
                {
                    _pathList.RemoveAt(index);
                    listView.VirtualListSize--;
                    theIndex = index;
                }
                _pathSet.Remove(path);
                _itemCache.Remove(path);
                OnIndexRemoved(new IndexEventArgs(index));
            }
            return theIndex;
        }

        public int Rename(string srcPath, string dstPath)
        {
            int theIndex = -1;
            if (!Contains(dstPath))
            {
                theIndex = IndexOf(srcPath);
                if (theIndex != -1)
                {
                    IsRenamingFile = true;
                    lock (_pathList)
                    {
                        _pathList[theIndex] = dstPath;
                    }
                    _pathSet.Remove(srcPath);
                    _pathSet.Add(dstPath);
                    _itemCache.Remove(srcPath);
                    // Do not remove the thumb, or the index in ImageList will be upset. 
                    // If the file name be used again(cancel the renaming or another file is added), the thumb in ImageList will be replaced.
                    IsRenamingFile = false;

                    OnRenamed(new RenamedEventArgs(srcPath, dstPath));
                }
            }
            return theIndex;
        }

        public void EnsureVisible(int index)
        {
            if (index >= 0 && index < Count)
            {
                listView.EnsureVisible(index);
            }
        }

        public void EnsureVisible(string path)
        {
            EnsureVisible(IndexOf(path));
        }

        private void SelectIndex(int index, bool selected)
        {
            IsSelectingListView = true;
            int listSize = Count;
            if (index >= 0 && index < listSize)
            {
                if (selected)
                    listView.SelectedIndices.Add(index);
                else
                    listView.SelectedIndices.Remove(index);
            }
            IsSelectingListView = false;
        }

        private void SelectIndices(IEnumerable<int> indices, bool selected)
        {
            IsSelectingListView = true;
            int listSize = Count;
            foreach (int anIndex in indices)
            {
                if (anIndex >= 0 && anIndex < listSize)
                {
                    if (selected)
                        listView.SelectedIndices.Add(anIndex);
                    else
                        listView.SelectedIndices.Remove(anIndex);
                }
            }
            IsSelectingListView = false;
        }

        private void SelectPath(string path, bool selected)
        {
            if (String.IsNullOrEmpty(path))
                return;

            IsSelectingListView = true;
            int listSize = Count;
            for (int i = 0; i < listSize; i++)
            {
                if (_pathList[i].Equals(path))
                {
                    if (selected)
                        listView.SelectedIndices.Add(i);
                    else
                        listView.SelectedIndices.Remove(i);

                    break;
                }
            }
            IsSelectingListView = false;
        }

        private void SelectPaths(IEnumerable<string> paths, bool selected)
        {
            IsSelectingListView = true;
            List<string> pathList = new List<string>(paths);
            int listSize = Count;
            string currentPath = null;
            for (int i = 0; i < listSize; i++)
            {
                currentPath = _pathList[i];
                if (pathList.Contains(currentPath))
                {
                    if (selected)
                        listView.SelectedIndices.Add(i);
                    else
                        listView.SelectedIndices.Remove(i);

                    pathList.Remove(currentPath);
                    if (pathList.Count == 0)
                        break;
                }
            }
            IsSelectingListView = false;
        }

        #endregion

        #region Event Trigger Methods

        protected void OnSelectedChanged(EventArgs e)
        {
            if (SelectedChanged != null)
                SelectedChanged(this, e);
        }

        protected void OnPathCleared(EventArgs e)
        {
            if (PathCleared != null)
                PathCleared(this, e);
        }

        protected void OnPathAdded(PathEventArgs e)
        {
            if (PathAdded != null)
                PathAdded(this, e);
        }

        protected void OnPathsAdded(PathsEventArgs e)
        {
            if (PathsAdded != null)
                PathsAdded(this, e);
        }

        protected void OnPathInserted(PathInsertedEventArgs e)
        {
            if (PathInserted != null)
                PathInserted(this, e);
        }

        protected void OnPathRemoved(PathEventArgs e)
        {
            if (PathRemoved != null)
                PathRemoved(this, e);
        }

        protected void OnIndexRemoved(IndexEventArgs e)
        {
            if (IndexRemoved != null)
                IndexRemoved(this, e);
        }

        protected void OnRenamed(RenamedEventArgs e)
        {
            if (Renamed != null)
                Renamed(this, e);
        }

        #endregion

        #region ListViewItem Methods

        private ListViewItem CreateThumbItem(string filePath)
        {
            ListViewItem item = new ListViewItem();
            item.Name = filePath;
            item.Text = Path.GetFileName(filePath);

            if (_fileNameAttribute != null)
                _fileNameAttribute.AddSubItems(item, item.Text);

            if (ImageFileManager.IsImageFile(filePath))
                item.ImageIndex = largeImageList.Images.IndexOfKey(_imageFileThumbnailKey);
            else
                item.ImageIndex = largeImageList.Images.IndexOfKey(_dataFieThumbnailKey);

            _itemCache.Add(filePath, item);
            WriteThumbFiles(filePath);

            return item;
        }

        private bool IsThumbFilesEmpty()
        {
            if (_virtualModeLastThumbFirstDraw)
                return _pathStack.Count == 0;
            else
                return _pathQueue.Count == 0;
        }

        private void WriteThumbFiles(params string[] files)
        {
            if (_virtualModeLastThumbFirstDraw)
                lock (_pathStack)
                {
                    foreach (string aFile in files)
                    {
                        _pathStack.Push(aFile);
                    }
                }
            else
                lock (_pathQueue)
                {
                    foreach (string aFile in files)
                    {
                        _pathQueue.Enqueue(aFile);
                    }
                }

            if (!thumbnailBackgroundWorker.IsBusy)
                thumbnailBackgroundWorker.RunWorkerAsync();
        }

        private string ReadThumbFiles()
        {
            if (_virtualModeLastThumbFirstDraw)
                lock (_pathStack)
                {
                    if (_pathStack.Count != 0)
                        return _pathStack.Pop();
                    else
                        return null;
                }
            else
                lock (_pathQueue)
                {
                    if (_pathQueue.Count != 0)
                        return _pathQueue.Dequeue();
                    else
                        return null;
                }
        }

        private Bitmap RetrieveIconImageByExtension(string filePath, Size imageSize)
        {
            string fileExtension = Path.GetExtension(filePath).ToLower();
            if (fileExtension.ToLower().Equals(".exe") || fileExtension.ToLower().Equals(".lnk"))
            {
#if(!Limit)
                Icon fileIcon = null;
                try
                {
                    Icon jumboIcon = ApiFileInformation.FileJumboIcon(filePath, imageSize);
                    fileIcon = (Icon)jumboIcon.Clone();
                    jumboIcon.Dispose();
                }
                catch (Exception ex)
                {
                    ExceptionReporter.ReportException(ex);
                    fileIcon = null;
                }

                if (fileIcon == null)
                    fileIcon = Icon.ExtractAssociatedIcon(filePath);
#else
                Icon fileIcon = Icon.ExtractAssociatedIcon(filePath);
#endif

                Bitmap currentImage = fileIcon.ToBitmap();

                Bitmap shownBitmap = new Bitmap(imageSize.Width, imageSize.Height);
                Graphics graphics = Graphics.FromImage(shownBitmap);

                float scaleWidth = (float)(currentImage.Width) / (float)imageSize.Width;
                float scaleHeight = (float)(currentImage.Height) / (float)imageSize.Height;
                float scale = Math.Max(scaleWidth, scaleHeight);

                float displayWidth = currentImage.Width;
                float displayHeight = currentImage.Height;
                if (scale > 1.0)
                {
                    displayWidth = displayWidth / scale;
                    displayHeight = displayHeight / scale;
                }

                float startX = (imageSize.Width - displayWidth) / 2;
                float startY = (imageSize.Height - displayHeight) / 2;

                graphics.DrawImage(currentImage, startX, startY, displayWidth, displayHeight);
                currentImage.Dispose();

                return shownBitmap;
            }
            else
            {
                string imageKey = String.Concat(fileExtension, imageSize.ToString());

                if (_iconTable.ContainsKey(imageKey))
                {
                    return (Bitmap)((Bitmap)_iconTable[imageKey]).Clone();
                }
                else
                {
#if(!Limit)
                    Icon fileIcon = null;
                    try
                    {
                        Icon jumboIcon = ApiFileInformation.FileJumboIcon(filePath, imageSize);
                        fileIcon = (Icon)jumboIcon.Clone();
                        jumboIcon.Dispose();
                    }
                    catch (Exception ex)
                    {
                        ExceptionReporter.ReportException(ex);
                        fileIcon = null;
                    }
                    if (fileIcon == null)
                        fileIcon = Icon.ExtractAssociatedIcon(filePath);
#else
                    Icon fileIcon = Icon.ExtractAssociatedIcon(filePath);
#endif

                    Bitmap currentImage = fileIcon.ToBitmap();

                    Bitmap shownBitmap = new Bitmap(imageSize.Width, imageSize.Height);
                    Graphics graphics = Graphics.FromImage(shownBitmap);

                    float scaleWidth = (float)(currentImage.Width) / (float)imageSize.Width;
                    float scaleHeight = (float)(currentImage.Height) / (float)imageSize.Height;
                    float scale = Math.Max(scaleWidth, scaleHeight);

                    float displayWidth = currentImage.Width;
                    float displayHeight = currentImage.Height;
                    if (scale > 1.0)
                    {
                        displayWidth = displayWidth / scale;
                        displayHeight = displayHeight / scale;
                    }

                    float startX = (imageSize.Width - displayWidth) / 2;
                    float startY = (imageSize.Height - displayHeight) / 2;

                    graphics.DrawImage(currentImage, startX, startY, displayWidth, displayHeight);
                    currentImage.Dispose();

                    _iconTable.Add(imageKey, shownBitmap);
                    return shownBitmap;
                }
            }
        }

        private Bitmap CreateThumbnail(string filePath, bool useImageIcon, Size imageSize)
        {
            if (!File.Exists(filePath))
            {
                Debug.Fail("Fail to creating the thumbnail.", String.Concat("File does not exist: ", filePath));
                return null;
            }

            string fileExtension = Path.GetExtension(filePath).ToLower();

            if (useImageIcon && ImageFileManager.IsImageExtension(fileExtension))
            {
                Image currentImage;
                try
                {
                    currentImage = Image.FromFile(filePath);
                }
                catch (Exception ex)
                {
                    ExceptionReporter.ReportException(ex);
                    return null;
                }

                Bitmap resultBitmap = new Bitmap(imageSize.Width, imageSize.Height);
                Graphics graphics = Graphics.FromImage(resultBitmap);

                float scaleWidth = (float)(currentImage.Width) / (float)imageSize.Width;
                float scaleHeight = (float)(currentImage.Height) / (float)imageSize.Height;
                float scale = Math.Max(scaleWidth, scaleHeight);

                float displayWidth = currentImage.Width;
                float displayHeight = currentImage.Height;
                if (scale > 1.0)
                {
                    displayWidth = displayWidth / scale;
                    displayHeight = displayHeight / scale;
                }
                if (displayWidth < 1.0f)
                    displayWidth = 1.0f;
                if (displayHeight < 1.0f)
                    displayHeight = 1.0f;

                float startX = (imageSize.Width - displayWidth) / 2;
                float startY = (imageSize.Height - displayHeight) / 2;

                if (displayWidth <= 120 && displayHeight <= 120)
                {
                    // If the thumbnail icon is not larger than 120*120, try to use the embeded thumbnail image.
                    Image.GetThumbnailImageAbort callback = new Image.GetThumbnailImageAbort(delegate() { return false; });
                    Image thumbnailImage = currentImage.GetThumbnailImage((int)displayWidth, (int)displayHeight, callback, IntPtr.Zero);
                    graphics.DrawImage(thumbnailImage, startX, startY, displayWidth, displayHeight);
                    thumbnailImage.Dispose();
                    currentImage.Dispose();
                }
                else
                {
                    // Draw thumbnail by original image.
                    graphics.DrawImage(currentImage, startX, startY, displayWidth, displayHeight);
                    currentImage.Dispose();
                }

                return resultBitmap;
            }
            else
            {
                Bitmap resultBitmap = RetrieveIconImageByExtension(filePath, imageSize);
                return resultBitmap;
            }
        }

        private void RefreshThumbnail(int index)
        {
            if (index >= 0 && index < Count)
            {
                IsRefreshingThumbnail = true;
                ListViewItem item = listView.Items[index];
                bool itemSelected = item.Selected;
                if (itemSelected)
                {
                    item.Selected = false;
                    item.Selected = true;
                }
                else
                {
                    item.Selected = true;
                    item.Selected = false;
                }
                IsRefreshingThumbnail = false;
            }
        }

        private void ClearImageList(ImageList imageList)
        {
            List<Image> clearImageList = new List<Image>(imageList.Images.Count);
            foreach (Image anImage in imageList.Images)
                clearImageList.Add(anImage);
            imageList.Images.Clear();

            System.Threading.ParameterizedThreadStart threadStart = new System.Threading.ParameterizedThreadStart(
                delegate(object theImageList)
                {
                    foreach (Image anImage in (List<Image>)theImageList)
                    {
                        anImage.Dispose();
                    }
                });
            System.Threading.Thread clearThread = new System.Threading.Thread(threadStart);
            clearThread.Start(clearImageList);
        }

        #endregion

        #region listView Event Handler Methods

        private void listView_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (IsSelectingListView || IsRenamingFile || IsRefreshingThumbnail)
                return;

            OnSelectedChanged(EventArgs.Empty);
        }

        private void listView_CacheVirtualItems(object sender, CacheVirtualItemsEventArgs e)
        {
            int itemCount = e.EndIndex - e.StartIndex + 1;

            lock (_itemCache)
            {
                if (_virtualModeLastThumbFirstDraw)
                    for (int i = itemCount - 1; i >= 0; i--)
                    {
                        // The stack is used as cache of file to load, so the files be retrieved lastly will be load firstly.
                        // But the first one of last retrieved files should be load firstly.
                        string filePath = _pathList[e.StartIndex + i];
                        if (!_itemCache.ContainsKey(filePath))
                        {
                            ListViewItem item = CreateThumbItem(filePath);
                        }
                    }
                else
                    for (int i = 0; i < itemCount; i++)
                    {
                        // The queue is used as cache of file to load, so the files be retrieved firstly will be load firstly.
                        string filePath = _pathList[e.StartIndex + i];
                        if (!_itemCache.ContainsKey(filePath))
                        {
                            ListViewItem item = CreateThumbItem(filePath);
                        }
                    }
            }
        }

        private void listView_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
        {
            ListView self = sender as ListView;
            string filePath = _pathList[e.ItemIndex];
            lock (_itemCache)
            {

                if (_itemCache.Contains(filePath))
                {
                    ListViewItem item = (ListViewItem)_itemCache[filePath];
                    e.Item = item;
                }
                else
                {
                    ListViewItem item = CreateThumbItem(filePath);
                    e.Item = item;
                }
            }
        }

        private void listView_SearchForVirtualItem(object sender, SearchForVirtualItemEventArgs e)
        {
            int listLength = _pathList.Count;
            for (int i = e.StartIndex; i < listLength - e.StartIndex; i++)
            {
                if (Path.GetFileName(_pathList[i]).Contains(e.Text))
                {
                    e.Index = i;
                    break;
                }
            }
        }

        #endregion

        #region thumbnailBackgroundWorkder Event Handler Methods

        private void thumbnailBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker self = sender as BackgroundWorker;

            string filePath = ReadThumbFiles();

            Bitmap smallThumb = CreateThumbnail(filePath, _smallThumbnailIcon, smallImageList.ImageSize);
            Bitmap largeThumb = CreateThumbnail(filePath, _largeThumbnailIcon, largeImageList.ImageSize);

            e.Result = new object[] { filePath, smallThumb, largeThumb };
        }

        private void thumbnailBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {

        }

        private void thumbnailBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                ExceptionReporter.ReportException(e.Error, "Error occurs when loading thumbnail.");
            }

            if (e.Result != null)
            {
                object[] result = (object[])e.Result;
                string imageKey = (string)result[0];
                Bitmap smallThumb = (Bitmap)result[1];
                Bitmap largeThumb = (Bitmap)result[2];

                if (smallThumb != null && largeThumb != null)
                {
                    if (largeImageList.Images.ContainsKey(imageKey))
                    {
                        int theIndex = largeImageList.Images.IndexOfKey(imageKey);
                        smallImageList.Images[theIndex] = smallThumb;
                        largeImageList.Images[theIndex] = largeThumb;
                    }
                    else
                    {
                        smallImageList.Images.Add(imageKey, smallThumb);
                        largeImageList.Images.Add(imageKey, largeThumb);
                    }
                    ListViewItem item = (ListViewItem)_itemCache[imageKey];
                    if (item != null)
                    {
                        item.ImageIndex = largeImageList.Images.IndexOfKey(imageKey);

                        Debug.Assert(item.ImageIndex == smallImageList.Images.IndexOfKey(imageKey), "Image Index error." + item.Name);

                        RefreshThumbnail(item.Index);
                    }
                }
            }

            BackgroundWorker self = sender as BackgroundWorker;
            if (!IsThumbFilesEmpty() && !self.IsBusy)
                self.RunWorkerAsync();
        }

        #endregion

        #region FileList Properties

        [Browsable(false)]
        public int Count
        {
            get
            {
                return listView.VirtualListSize;
            }
        }

        [Browsable(false)]
        public string[] Paths
        {
            get
            {
                int listSize = Count;
                string[] paths = _pathList.ToArray();
                return paths;
            }
        }

        [Browsable(false)]
        [DefaultValue(-1)]
        public int SelectedIndex
        {
            get
            {
                return GetSelectedIndex();
            }
            set
            {
                int[] selectedIndices = GetSelectedIndices();
                SelectIndices(selectedIndices, false);
                SelectIndex(value, true);
                EnsureVisible(value);
                OnSelectedChanged(EventArgs.Empty);
            }
        }

        [Browsable(false)]
        [DefaultValue(new int[0])]
        public int[] SelectedIndices
        {
            get
            {
                return GetSelectedIndices();
            }
            set
            {
                int[] selectedIndices = GetSelectedIndices();
                if (value == null)
                {
                    SelectIndices(selectedIndices, false);
                }
                else
                {
                    SelectIndices(selectedIndices.Except(value), false);
                    SelectIndices(value.Except(selectedIndices), true);
                    if (value.Length > 0)
                        EnsureVisible(value[0]);
                }
                OnSelectedChanged(EventArgs.Empty);
            }
        }

        [Browsable(false)]
        [DefaultValue(null)]
        public string SelectedPath
        {
            get
            {
                return GetSelectedPath();
            }
            set
            {
                string[] selectedPaths = GetSelectedPaths();
                SelectPaths(selectedPaths, false);
                SelectPath(value, true);
                EnsureVisible(value);
                OnSelectedChanged(EventArgs.Empty);
            }
        }

        [Browsable(false)]
        [DefaultValue(new string[0])]
        public string[] SelectedPaths
        {
            get
            {
                return GetSelectedPaths();
            }
            set
            {
                string[] selectedPaths = GetSelectedPaths();
                if (value == null)
                {
                    SelectPaths(selectedPaths, false);
                }
                else
                {
                    SelectPaths(selectedPaths.Except(value), false);
                    SelectPaths(value.Except(selectedPaths), true);
                    if (value.Length > 0)
                        EnsureVisible(value[0]);
                }
                OnSelectedChanged(EventArgs.Empty);
            }
        }

        public string this[int index]
        {
            get
            {
                return GetPathAt(index);
            }
        }

        /// <summary>
        /// Prepare to rename a file. When the theItem in fileThumbListView is renamed, event 'SelectedIndexChanged' should do nothing.
        /// </summary>
        private bool IsRenamingFile
        {
            get { return _isRenamingFile; }
            set { _isRenamingFile = value; }
        }

        /// <summary>
        /// The items of fileThumbListView are being selected by some method, and the event 'SelectedIndexChanged' should do noting.
        /// </summary>
        private bool IsSelectingListView
        {
            get { return _isSelectingListView; }
            set { _isSelectingListView = value; }
        }

        private bool IsRefreshingThumbnail
        {
            get { return _isRefreshingThumbnail; }
            set { _isRefreshingThumbnail = value; }
        }

        #endregion

        #region Thumbnail Properties

        /// <summary>
        /// Gets or sets a value indicating whether the last requested will be drawn firstly when there are many requested image.
        /// </summary>
        [Category("Thumbnail")]
        [Description("Whether the last requested will be drawn firstly when there are many requested image.")]
        [DefaultValue(true)]
        public bool VirtualModeLastThumbFirstDraw
        {
            get { return _virtualModeLastThumbFirstDraw; }
            set { _virtualModeLastThumbFirstDraw = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the large thumbnail icon is enabled.
        /// </summary>
        [Category("Thumbnail")]
        [Description("Whether the large thumbnail icon is enabled.")]
        [DefaultValue(true)]
        public bool LargeThumbnailIcon
        {
            get { return _largeThumbnailIcon; }
            set { _largeThumbnailIcon = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the small thumbnail icon is enabled.
        /// </summary>
        [Category("Thumbnail")]
        [Description("Whether the small thumbnail icon is enabled.")]
        [DefaultValue(true)]
        public bool SmallThumbnailIcon
        {
            get { return _smallThumbnailIcon; }
            set { _smallThumbnailIcon = value; }
        }

        /// <summary>
        /// Gets or sets the specified instance of FileNameAttribute.
        /// </summary>
        [Browsable(false)]
        [DefaultValue(null)]
        public FileNameAttribute FileNameAttribute
        {
            get { return _fileNameAttribute; }
            set
            {
                if (_fileNameAttribute != value)
                {
                    if (_fileNameAttribute != null)
                        FileNameAttribute.ClearColumns(listView);

                    _fileNameAttribute = value;
                    _fileNameAttribute.AddColumns(listView);
                }
            }
        }

        /// <summary>
        /// Gets or sets how items are displayed in the control.
        /// </summary>
        [Category("Thumbnail")]
        [Description("How items are displayed in the control.")]
        [DefaultValue(View.LargeIcon)]
        public View View
        {
            get { return listView.View; }
            set
            {
                if (value != View.Tile)
                    listView.View = value;
            }
        }

        /// <summary>
        /// Gets or sets the size of large thumbnails and icons.
        /// </summary>
        /// <remarks>This size will be used by LargeIcon, Tile view.</remarks>
        [Category("Thumbnail")]
        [Description("The size of large thumbnails and icons.")]
        [DefaultValue(typeof(Size), "128, 128")]
        public Size LargeIconSize
        {
            get { return largeImageList.ImageSize; }
            set { largeImageList.ImageSize = value; }
        }

        /// <summary>
        /// Gets or sets the size of small thumbnails and icons.
        /// </summary>
        /// <remarks>This size will be used by SmallIcon, List and Detail view.</remarks>
        [Category("Thumbnail")]
        [Description("The size of small thumbnails and icons.")]
        [DefaultValue(typeof(Size), "32, 32")]
        public Size SmallIconSize
        {
            get { return smallImageList.ImageSize; }
            set { smallImageList.ImageSize = value; }
        }

        /// <summary>
        /// Gets or sets the default icon of image file.
        /// </summary>
        [Category("Thumbnail")]
        [Description("The default icon of image file.")]
        [DefaultValue(null)]
        public Image ImageFileDefaultIcon
        {
            get { return _imageFileDefaultIcon; }
            set
            {
                _imageFileDefaultIcon = value;
                if (_imageFileDefaultIcon != null)
                {
                    largeImageList.Images.Add(_imageFileThumbnailKey, _imageFileDefaultIcon);
                    smallImageList.Images.Add(_imageFileThumbnailKey, _imageFileDefaultIcon);
                }
                else
                {
                    largeImageList.Images.RemoveByKey(_imageFileThumbnailKey);
                    smallImageList.Images.RemoveByKey(_imageFileThumbnailKey);
                }
            }
        }

        /// <summary>
        /// Gets or sets the default icon of data file.
        /// </summary>
        [Category("Thumbnail")]
        [Description("The default icon of data file.")]
        [DefaultValue(null)]
        public Image DataFileDefaultIcon
        {
            get { return _dataFileDefaultIcon; }
            set
            {
                _dataFileDefaultIcon = value;
                if (_dataFileDefaultIcon != null)
                {
                    largeImageList.Images.Add(_dataFieThumbnailKey, _dataFileDefaultIcon);
                    smallImageList.Images.Add(_dataFieThumbnailKey, _dataFileDefaultIcon);
                }
                else
                {
                    largeImageList.Images.RemoveByKey(_dataFieThumbnailKey);
                    smallImageList.Images.RemoveByKey(_dataFieThumbnailKey);
                }
            }
        }

        #endregion

        #region FileList Events

        [Category("File List")]
        public event EventHandler SelectedChanged;

        [Category("File List")]
        public event EventHandler PathCleared;

        [Category("File List")]
        public event PathEventHandler PathAdded;

        [Category("File List")]
        public event PathsEventHandler PathsAdded;

        [Category("File List")]
        public event PathInsertedEventHandler PathInserted;

        [Category("File List")]
        public event PathEventHandler PathRemoved;

        [Category("File List")]
        public event IndexEventHandler IndexRemoved;

        [Category("File List")]
        public event RenamedEventHandler Renamed;

        #endregion
    }
}
